Programming and management of experiments in oTree

Module 3: Group experiments

Authors
Affiliations

Matteo Ploner

Università di Trento

Luca Congiu

Università degli Studi di Roma “Tor Vergata”

Published

July 2025

Group experiments

  • Each player chooses independently
    • Payoffs are defined by parameters and by her own choice and the choices of the other players

Matching, roles, values

  • How many repetitions of an interaction are implemented?
    • One shot
    • Repeated
  • When repeated, how are people matched?
    • Partner matching
      • All subjects are matched with the same partner for the entire experiment
    • Random partner matching
      • Subjects are randomly matched in each repetition
  • Which roles do they have in the interaction?
    • Symmetric roles
      • All subjects are identical
    • Asymmetric roles
      • e.g., one subject is of type A and the other is of type B
  • Which values are associated to subjects?
    • Conditional on the role
      • e.g., type A has an endowment of 10 and Type B has an endowment of 5
    • Unconditional
      • e.g., all subjects have an endowment of 10

A working example

  • Consider 8 subjects that are matched in groups of two

  • One subject in each group is of type BLUE and the other of type GREEN

Repeated interaction

  • When the interaction is repeated we can have 4 possible combinations of type ad matching
Matching
Constant Varying
Type Constant CT/CM CT/VM
Varying VT/CM VT/VM
  • Changes in roles and matching can be explicitly modeled by design or be random
    • In the code provided below we randomize
      • Safe and common approach

Constant Type and Constant Matching (CT/CM)

  • Round 1

  • Round 2

  • Individuals are matched together for the entire experiment and keep the same role across repetitions

Varying Type and Constant Matching (VT/CM)

  • Round 1

  • Round 2

  • Individuals are matched together for the entire experiment but do not keep the same role across repetitions

Constant Type and Varying Matching (CT/VM)

  • Round 1

  • Round 2

  • Individuals are not matched together for the entire experiment but keep the same role across repetitions

Varying Type and Varying Matching (VT/VM)

  • Round 1

  • Round 2

  • Individuals are not matched together for the entire experiment and do not keep the same role across repetitions

Values

  • Participants are generally endowed with some values (attributes)
    • Unconditionally the same for all participants
      • e.g., endowment in the dictator game
    • Conditional upon some characteristic of the participant
      • e.g., efficiency factors the are related to previous performance in a task

Values: example

  • Green Players get an endowment of 0 Euro
  • Blue players get an endowment of 10 Euro

oTree code for roles, matching, and value assignment

_init_.py

  • Roles and matching are governed by the file _init_.py
    • Section models
    • This file manages the “structure” of your experiment
      • The “database”
  • 4 alternative protocols are presented here
    • Constant type and constant matching (CT/CM)
    • Varying type and constant matching (VT/CM)
    • Constant type and varying matching (CT/VM)
    • Varying type and varying matching (VT/VM)

Constant type and constant matching (CT/CM)

CT/CM

  • This is the code in _init_.py

class Constants(BaseConstants):
    name_in_url = 'groups_roles'
    players_per_group = 2
    num_rounds = 10
    matching = "Constant Type and Constant Matching (CT/CM)"

class Subsession(BaseSubsession):
    pass

def creating_session(subsession):
    if subsession.round_number == 1:  # this way we get a fixed role across repetitions
        subsession.group_randomly()
        print(subsession.get_group_matrix())
        for g in subsession.get_groups():
            for p in g.get_players():
                if p.id_in_group % 2 == 0:
                    p.type = 'BLUE'
                    p.value = cu(10) # assign the corresponding value
                else:
                    p.type = 'GREEN'
                    p.value = cu(0)# assign the corresponding value
    else:
        subsession.group_like_round(1)
        for g in subsession.get_groups():
            for p in g.get_players():
                p.type = p.in_round(subsession.round_number-1).type
                p.value = p.in_round(subsession.round_number-1).value

class Group(BaseGroup):
    pass

# needed to store values 

class Player(BasePlayer):
    type = models.StringField()
    id_oth = models.IntegerField()
    type_oth = models.StringField()
    value = models.CurrencyField()
    value_oth = models.CurrencyField()
  • This is the assignment to groups and roles we get (from the POW of Player 1)




CT/CM: commented code

class Constants(BaseConstants):
# define here the constants of the session
    name_in_url = 'groups_roles'
    # label that appears in browser
    players_per_group = 2
    # how many players in each group (important for matching!)
    num_rounds = 10
    # how many repetitions (important for matching!)
    matching = "Constant Type and Constant Matching (CT/CM)"
    # name of the matching protocol

class Subsession(BaseSubsession):
    pass

#group and types are defined in the Subsession class by the following function (method of the subsession class)
def creating_session(subsession):
#to set initial values in the subsession
#***************************************************************************
# START code to generate matching and roles in Round 1
#***************************************************************************
    if subsession.round_number == 1:
    # the following code is executed only id round is == 1
    # round_number -> is a built-in function that gives the current round number
        subsession.group_randomly()
        # group_randomly() -> built-in function that  shuffles players randomly
        for g in subsession.get_groups():
        # get_groups() -> returns a list of all the groups in the subsession.
        # loop through the groups in the subsession
            for p in g.get_players():
            # get_players() -> returns a list of all the players in the subsession.
            # loop through the players in the subsession (p is a player)
                if p.id_in_group % 2 == 0:
                # id_in_group -> player's attribute (unique identifier)
                # if the id is even (via modulo operator)
                    p.type = 'BLUE'
                    # the participant is assigned to type "BLUE"
                    # type is "initialized" in class player as a string
                    p.value = cu(10)
                    # the blues are assigned an endowment of 10 points
                    # value is "initialized" in class player as currency
                else:
                    # if the participant id is odd
                    p.participant.vars['type'] = 'GREEN'
                    # the participant is assigned to type "GREEN"
                    p.value = cu(0)
                    # the greens are assigned an endowment of 0 points
                    # value is "initialized" in class player as currency

#***************************************************************************
# END code to generate matching and roles in Round 1
#***************************************************************************
#***************************************************************************
# START code to generate matching and types in round >1
#***************************************************************************

        else:
          # if round is not round 1 (see the indenting)
            subsession.group_like_round(1)
            # perform matching like in round 1 (partner matching)
            for g in subsession.get_groups():
                for p in g.get_players():
                    p.type = p.in_round(subsession.round_number-1).type
                    p.value = p.in_round(subsession.round_number-1).value
#***************************************************************************
# END code to generate matching and roles
#***************************************************************************


class Player(BasePlayer):
    type = models.StringField() # this is a string variable that will be filled with player's type (see above)
    value = models.CurrencyField() # this is a currency variable that will be filled with player's endowment (see above)

class Group(BaseGroup):
    pass

Varying type and constant matching (VT/CM)

VT/CM


class Constants(BaseConstants):
    name_in_url = 'groups_roles'
    players_per_group = 2
    num_rounds = 10
    matching = " Varying Type and Constant Matching (VT/CM)"

import random
class Subsession(BaseSubsession):
    pass

def creating_session(subsession):
    if subsession.round_number == 1: # this way we get a fixed role across repetitions
        subsession.group_randomly()# built-in function
        print(subsession.get_group_matrix())
        rdm=random.randint(0, 1)
        print(rdm)
        for g in subsession.get_groups():
            for p in g.get_players():
                if rdm==1: #this way we randomize role accordin to id in group
                    if p.id_in_group % 2 == 0:
                        p.type = 'BLUE'
                        p.value = cu(10)# assign the corresponding value
                    else:
                        p.type = 'GREEN'
                        p.value = cu(0)   # assign the corresponding value
                else:
                    if p.id_in_group % 2 == 0:
                        p.type = 'GREEN'
                        p.value = cu(0)
                    else:
                        p.type = 'BLUE'
                        p.value = cu(10)
    else:
        subsession.group_like_round(1)
        rdm=random.randint(0, 1)
        print(rdm)
        for g in subsession.get_groups():
            for p in g.get_players():
                if rdm==1: #this way we randomize role accordin to id in group
                    if p.id_in_group % 2 == 0:
                        p.type = 'BLUE'
                        p.value = cu(10)
                    else:
                        p.type = 'GREEN'
                        p.value = cu(0)
                else:
                    if p.id_in_group % 2 == 0:
                        p.type = 'GREEN'
                        p.value = cu(0)
                    else:
                        p.type = 'BLUE'
                        p.value = cu(10)
        print(subsession.get_group_matrix())

class Player(BasePlayer):
    type = models.StringField() # this is a string variable that will be filled with player's type (see above)
    value = models.CurrencyField() # this is a currency variable that will be filled with player's endowment (see above)

class Group(BaseGroup):
    pass
  • This is the assignment to groups and roles we get (from the POW of Player 1)





VT/CM: commented code

class Constants(BaseConstants):
# define here the constants of the session
    name_in_url = 'groups_roles'
    # label that appears in browser
    players_per_group = 2
    # how many players in each group (important for matching!)
    num_rounds = 10
    # how many repetitions (important for matching!)
    matching = " Varying Type and Constant Matching (VT/CM)"
    # matching protocol

import random
# import module random
class Subsession(BaseSubsession):

#group and types are defined in the Subsession class by the following function (method of the subsession class)
def creating_session(subsession):
#to set initial values in the subsession
#***************************************************************************
# START code to generate matching and types in round 1
#***************************************************************************
    if subsession.round_number == 1:
    # the following code is executed only id round is == 1
    # round_number -> is a built-in function that gives the current round number
        subsession.group_randomly()# built-in function
        # group_randomly() -> built-in function that  shuffles players randomly
        rdm=random.randint(0, 1)
        # assign a random value, either 0 or 1, to variable rdm
        for g in subsession.get_groups():
        #get_groups() -> returns a list of all the groups in the subsession.
            # loop through the groups in the subsession
            for p in g.get_players():
            # get_players() -> returns a list of all the players in the subsession.
            # loop through the players in the subsession (p is a player)
#***************************************************************************
# matching and types when random is 1 - > even = BLUE, odd= GREEN
#***************************************************************************
                if rdm==1:
                # the following code is executed if rdm is 1
                    if p.id_in_group % 2 == 0:
                    # id_in_group -> player's attribute (unique identifier)
                    # if the id is even (via modulo operator)
                        p.type = 'BLUE'
                        # the participant is assigned to type "BLUE"
                        p.value = c(10)# assign the corresponding value
                        # the participant is assigned the corresponding endowment
                    else:
                    # if the participant id is odd
                        p.type = 'GREEN'
                        # the participant is assigned to type "GREEN"
                        p.value = c(0)# assign the corresponding value
                        # the participant is assigned the corresponding endowment

#***************************************************************************
# matching and types when random is 1 - > even = GREEN, odd= BLUE
#***************************************************************************
                else:
                # the following code is executed if rdm is 0
                    if p.id_in_group % 2 == 0:
                    # see comment above
                        p.type = 'GREEN'
                        # see comment above
                        p.value = c(0)# assign the corresponding value
                        # the participant is assigned the corresponding endowment
                    else:
                        p.type = 'BLUE'
                        # see comment above
                        p.value = c(10)# assign the corresponding value
                        # the participant is assigned the corresponding endowment
#***************************************************************************
# END code to generate matching and types in round 1
#***************************************************************************
#***************************************************************************
# START code to generate matching and types in round >1
#***************************************************************************
    else:
    # if round is not round 1 (see the indenting)
        subsession.group_like_round(1)
        # perform matching like in round 1 (partner matching)
        rdm=random.randint(0, 1)
        # here we run the same code as in round 1 to randomly generate types
        for g in subsession.get_groups():
            for p in g.get_players():
                if rdm==1:
                    if p.id_in_group % 2 == 0:
                        p.type = 'BLUE'
                    else:
                        p.type = 'GREEN'
                else:
                    if p.id_in_group % 2 == 0:
                        p.type = 'GREEN'
                    else:
                        p.type = 'BLUE'
#***************************************************************************
# END code to generate matching and types
#***************************************************************************

class Group(BaseGroup):
    pass

# needed to store values

class Player(BasePlayer):
    type = models.StringField()
    value = models.CurrencyField()

Constant Type and Varying Matching (CT/VM)

CT/VM

class Constants(BaseConstants):
    name_in_url = 'groups_roles'
    players_per_group = 2
    num_rounds = 10
    matching = "Constant Type and Varying Matching (CT/VM)"

class Subsession(BaseSubsession):
    pass

def creating_session(subsession):
    subsession.group_randomly(fixed_id_in_group=True)# built-in function
    print(subsession.get_group_matrix())
    for g in subsession.get_groups():
        for p in g.get_players():
            if p.id_in_group % 2 == 0:
                p.type = 'BLUE'
                p.value = cu(10)
            else:
                p.type = 'GREEN'
                p.value = cu(0)

#To assign values in each round, the blue get 10 and the green get 0
    for p in subsession.get_players():
        if p.type == 'BLUE':
            p.value = cu(10)
        else:
            p.value = cu(0)

class Player(BasePlayer):
    type = models.StringField()

class Group(BaseGroup):
    pass
  • This is the assignment to groups and roles we get (from the POW of Player 1)





CT/VM: commented code

class Constants(BaseConstants):
# define here the constants of the session
    name_in_url = 'groups_roles'
    # label that appears in browser
    players_per_group = 2
    # how many players in each group (important for matching!)
    num_rounds = 10
    # how many repetitions (important for matching!)
    matching = "Varying Type and Varying Matching (VT/VM)"
    # name of matching protocol

class Subsession(BaseSubsession):
    pass

#group and types are defined in the Subsession class by the following function (method of the subsession class)
def creating_session(subsession):
#to set initial values in the subsession
    subsession.group_randomly(fixed_id_in_group=True)
    #group_randomly(fixed_id_in_group=True) -> built-in function that  shuffles players randomly but keeps the id constant
    for g in subsession.get_groups():
    # get_groups() -> returns a list of all the groups in the subsession.
    # loop through the groups in the subsession
        for p in g.get_players():
        # get_players() -> returns a list of all the players in the subsession.
        # loop through the players in the subsession (p is a player)
            if p.id_in_group % 2 == 0:
                # id_in_group -> player's attribute (unique identifier)
                # if the id is even (via modulo operator)
                p.type = 'BLUE'
                # the participant is assigned to type "BLUE"
                p.value = c(10)
                # the corresponding endowment
            else:
            # if the participant id is odd
                p.type = 'GREEN'
                # the participant is assigned to type "GREEN"
                p.value = c(0)
                # the corresponding endowment

class Player(BasePlayer):
type = models.StringField()
value = models.CurrencyField()

class Group(BaseGroup):
pass

Varying Type and Varying Matching (VT/VM)

VT/VM

class Constants(BaseConstants):
    name_in_url = 'groups_roles'
    players_per_group = 2
    num_rounds = 10
    matching = "Varying Type and Varying Matching (VT/VM)"

class Subsession(BaseSubsession):
    pass

def creating_session(subsession):
    subsession.group_randomly()  # built-in function
    rdm = random.randint(0, 1)
    print(rdm)
    for g in subsession.get_groups():
        for p in g.get_players():
            if rdm == 1:  # this way we randomize role according to id in group
                if p.id_in_group % 2 == 0:
                    p.type = 'BLUE'
                    p.value = cu(10)
                else:
                    p.type = 'GREEN'
                    p.value = cu(0)
            else:
                if p.id_in_group % 2 == 0:
                    p.type = 'GREEN'
                    p.value = cu(0)
                else:
                    p.type = 'BLUE'
                    p.value = cu(10)
    print(subsession.get_group_matrix())

class Player(BasePlayer):
    type = models.StringField()
    value = models.CurrencyField()

class Group(BaseGroup):
    pass




VT/VM: commented code

class Constants(BaseConstants):
# define here the constants of the session
    name_in_url = 'groups_roles'
    # label that appears in browser
    players_per_group = 2
    # how many players in each group (important for matching!)
    num_rounds = 2
    # how many repetitions (important for matching!)
    matching = "Varying Type and Varying Matching (VT/VM)"
    # matching protocol

import random
class Subsession(BaseSubsession):
    pass

#group and types are defined in the Subsession class by the following function (method of the subsession class)
def creating_session(subsession):
#to set initial values in the subsession
    subsession.group_randomly()
    #group_randomly() -> built-in function that  shuffles players
    rdm=random.randint(0, 1)
    # assign a random value, either 0 or 1, to variable rdm
    for g in subsession.get_groups():
    #get_groups() -> returns a list of all the groups in the subsession.
    # loop through the groups in the subsession
        for p in g.get_players():
        # get_players() -> returns a list of all the players in the subsession.
        # loop through the players in the subsession (p is a player)
#***************************************************************************
# matching and types when random is 1 - > even = BLUE, odd= GREEN
#***************************************************************************
            if rdm==1:
            # the following code is executed if rdm is 1
                if p.id_in_group % 2 == 0:
                # id_in_group -> player's attribute (unique identifier)
                # if the id is even (via modulo operator)
                    p.type = 'BLUE'
                    # the participant is assigned to type "BLUE"
                    p.value = c(10)
                else:
                # if the participant id is odd
                    p.type = 'GREEN'
                    # the participant is assigned to type "GREEN"
                    p.value = c(0)
#***************************************************************************
# matching and types when random is 1 - > even = GREEN, odd= BLUE
#***************************************************************************
            else:
            # the following code is executed if rdm is 0
                if p.id_in_group % 2 == 0:
                # see comment above
                    p.type = 'GREEN'
                    # see comment above
                    p.value = c(0)
                else:
                # see comment above
                    p.type = 'BLUE'
                    # see comment above
                    p.value = c(10)
#***************************************************************************
# END code to generate matching and types
#***************************************************************************

class Group(BaseGroup):
    pass

class Player(BasePlayer):
    type = models.StringField()
    value = models.CurrencyField()
# needed to store values 

A Public Goods Game

Setting

  • Voluntary Contribution game

  • Participants can decide how much contribute to a “public” project out of their endowment
  • Participants are in a group of N subjects (usually 4)
    • What is contributed is multiplied by an efficiency factor \(1/N < \alpha < 1\)
    • What is not contributed is kept in a private account
  • Private incentives are to contribute nothing to the public account
    • But, contributions are efficient
      • \(\Rightarrow\) social dilemma

Parameters

  • The interaction is repeated 10x in a partner fashion
    • Total earnings are given by the sum of earnings in each stage
  • Groups of 3
    • Matched together for the entire experiment (partner)
  • The efficiency factor \(\alpha=2/3\)
  • The initial endowment is E=100
    • The individual payoff function is
      • \(\Pi_i=E-c_i+\alpha \sum_j^N c_j\)
        • where, \(j\) are the members of \(i\)’s group (\(i\) include)
        • \(\sum_j^N c_j\) is the size of the public project
  • Choices are in integer steps
    • \(c_i \in \{0, 100\}\)

App

Screens

VC_1.png VC_2.png

VC_3.png VC_4.png

_inity_ (models): Constants

  • Set here a few important constants
    • Players per group
    • Number of rounds
    • Individual endowment
    • Multiplier for the public project
      • \(\alpha=\frac{2=multiplier}{3=group~members}\)
class C(BaseConstants):
    NAME_IN_URL = 'PGG'  # App name for URL
    PLAYERS_PER_GROUP = 3  # Number of players per group
    NUM_ROUNDS = 4  # Number of rounds
    ENDOWMENT = cu(100)  # Each player's initial endowment
    MULTIPLIER = 2  # Multiplier for the public good

_init.py_ (models): Matching

  • A function that defines matching with reference to class Subsession
    • No need to define roles in PGG
  • Partner matching
    • Random first round and then as first round
# Group players randomly in round 1, keep same groups in later rounds
def creating_session(subsession: Subsession):
    if subsession.round_number == 1:
        subsession.group_randomly()
    else:
        subsession.group_like_round(1)

_init.py_ (models): Compute payoffs

  • A function that defines matching with reference to class Group
    • Typical of strategic interaction, while in individual decision making they are defined in Player
class Group(BaseGroup):
    total_choices = models.CurrencyField()  # Total contributions in the group
    individual_share = models.CurrencyField()  # Each player's share from the public good
    total_earnings = models.CurrencyField()  # Total earnings from the public good

# Calculate payoffs for each group after all have contributed
def set_payoffs(group: Group):
    players = group.get_players()
    choices = [p.choice for p in players]  # List of all contributions
    print(choices)
    group.total_choices = sum(choices)  # Total group contribution
    group.total_earnings = group.total_choices * C.MULTIPLIER  # Public good total
    group.individual_share = group.total_earnings / C.PLAYERS_PER_GROUP  # Each player's share
    for p in players:
        # Each player's payoff: what they kept + their share from the public good
        p.payoff = C.ENDOWMENT - p.choice + group.individual_share

_init.py_ (models): Compute final payoff

  • Final payoff is the sum of payoffs in all rounds

[...]      

    # At the last round, sum up all payoffs for final result
    if group.round_number == C.NUM_ROUNDS:
        for p in players:
            hist = p.in_all_rounds()
            p.payoff_final = sum([g.payoff for g in hist])

_init.py_ (models): Player’s variables

  • Variables for players

[...]      

# Player class: stores player-level variables for each round
class Player(BasePlayer):
    choice = models.CurrencyField(min=0, max=C.ENDOWMENT)  # Player's contribution
    payoff_final = models.CurrencyField()  # Sum of payoffs across all rounds

_init.py_ (pages):

  • We have 5 pages
  • page_sequence = [Instructions, Contribute, ResultsWaitPage, Results, FinalResults]
    • Instructions(Page)
      • Displayed only in round 1
    • Contribute(Page)
    • ResultsWaitPage(WaitPage)
      • WaitPage → oTree waits until all players in the group have arrived at that point in the sequence, and then all players are allowed to proceed
        • Important that we have all data before computing the payoffs!
    • Results(Page)
    • FinalResults(Page)
      • Displayed only in last round

page_sequence = [Instructions, Contribute, ResultsWaitPage, Results, FinalResults]

Intructions(Page)

# Instructions page: only shown in round 1
class Instructions(Page):
    @staticmethod
    def is_displayed(player: Player):
        return player.round_number == 1
    @staticmethod
    def vars_for_template(player: Player):
        return {
            'common_proj': C.ENDOWMENT * C.PLAYERS_PER_GROUP,  # Total possible group endowment
        }

Contribute(Page)

  • Contribute
    • Collect own contribution to the public project
# Contribution page: player chooses how much to contribute
class Contribute(Page):
    form_model = 'player'
    form_fields = ['choice']
    @staticmethod
    def vars_for_template(player: Player):
        return {
            'round_number': player.round_number,
            'endowment': C.ENDOWMENT,
        }

ResultsWaitPage(WaitPage)

  • Need to “gather” all members of the group
    • When all players arrive you apply the set_payoffs method (see above)
  • If you do not use a waitpage you will not be able to get all chocies in a group!
# Wait page: waits for all players to contribute, then calculates payoffs
class ResultsWaitPage(WaitPage):
    @staticmethod
    def after_all_players_arrive(group: Group):
        set_payoffs(group)

Results(Page)

  • Display results of current round
  • Display history of choices
    • A dynamic table
# Results page: shows outcome for the current round and history
class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        hist = player.in_all_rounds()  # All rounds for this player
        # Safely get group-level attributes for each round
        history_contrib = [getattr(g.group, 'total_choices', 0) for g in hist]  # List of total group contributions per round
        # If you use a graph, you may need to import safe_json or use json.dumps
        try:
            from otree.api import safe_json
            history_contrib_json = safe_json(history_contrib)  # Convert to JSON for JavaScript graphing
        except ImportError:
            import json
            history_contrib_json = json.dumps(history_contrib)  # Fallback if safe_json not available
        data_hist = [
            [g.choice for g in hist],  # Player's contributions per round
            [getattr(g.group, 'individual_share', 0) for g in hist],  # Player's share from public good per round
            [g.payoff for g in hist]  # Player's payoff per round
        ]
        print(data_hist)  # For debugging: print the data structure
        table_hist = []
        for j in range(0, player.round_number):
            t = [j+1]  # Round number (1-based)
            for i in data_hist:
                t.append(i[j])  # Add each data point for this round
            table_hist.append(t)  # Add row to history table
        amount_kept = C.ENDOWMENT - player.choice  # What player kept this round
        return {
            'history_contrib': history_contrib_json,  # JSON for graph
            'round_number': player.round_number,      # Current round
            'kept': amount_kept,                      # Amount kept this round
            'table_hist': table_hist                  # Table of history for template
        }

FinalResults(Page)

  • Final results
    • Cumulative payment

class FinalResults(Page):  # Page to display final cumulative results
    @staticmethod
    def is_displayed(player: Player):  # Only show this page in the last round
        return player.round_number == C.NUM_ROUNDS  # True if current round is the last one
    @staticmethod
    def vars_for_template(player: Player):  # Variables passed to the template
        return {'payoff_final': player.payoff_final}  # Player's total payoff across all rounds

Appendix

Assignment 1

Objective

Explore the effect of different group matching protocols.

Tasks

  • Using the groups_roles app, implement this matching protocols:
    • Constant Matching (Partner Matching): for round 1 to 3 Groups are formed randomly in round 1,2, and 3
    • Varying Matching (Stranger Matching): for round 4 to 6 Groups are randomly re-formed in every round.

Assignment 2

Objective

Change the payment rule in the PGG app so that, instead of summing payoffs across all rounds, only one round is randomly selected for payment at the end of the experiment.

Tasks

  • Modify the PGG app code to randomly select one round for payment for each participant.
  • Update the FinalResults(Page) so it displays only the payoff from the selected round.
  • Update the instructions to explain the new payment rule.
  • Test your implementation to ensure only the selected round’s payoff is paid and displayed.

Code

  • The oTree apps of this lecture:

groups_roles



PGG